/* logfiledecoder.cpp
 * Class LogFileDecoder inherits QThread
 * Separate thread for converting log files (.fsl) to (.csv) format
 * Used from MainWindow when the "Convert Log" action is triggered
 * Works even when target is disconnected
 * Uses same algorithm as previous fccif test harness
 */

#include <QMutex>
#include <QFile>
#include <QDateTime>
#include <QDebug>

#include "logfiledecoder.h"

//Constructor, called from main GUI thread
LogFileDecoder::LogFileDecoder(QObject *parent) : QThread(parent)
{
    mutex = new QMutex(QMutex::NonRecursive);
    //Make sure pointers are zero so we can new() them at the right time
    logFile = 0;
    csvFile = 0;

    quit = false;
}

//Destructor, called from main GUI thread
LogFileDecoder::~LogFileDecoder()
{
    if (isRunning())
        abortDecode();

    delete mutex;
    if (logFile)
        delete logFile;
    if (csvFile)
        delete csvFile;
}

//Called by the GUI thread to start a decode process
// filename is for the log file (.fsl) to convert
void LogFileDecoder::requestDecode(QString filename)
{
    //Open the logfile if it exists (which it should since it was picked from a file dialog)
    mutex->lock();
    if (!logFile) {
        logFile = new QFile(filename);
    } else {
        logFile->setFileName(filename);
    }
    if (logFile->exists()) {
        logFile->open(QIODevice::ReadOnly | QIODevice::Text);
    }

    //Create a temporary csv file as we need to insert the headers before creating the final file
    QString csvFileName = logFile->fileName().replace("fsl","csv~1~");

    if (!csvFile) {
        csvFile = new QFile(csvFileName);
    } else {
        csvFile->setFileName(csvFileName);
    }

    csvFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
    output.setDevice(csvFile);

    quit = false;
    mutex->unlock();

    //Start the decode thread to do the work
    if (!isRunning())
        start();
}

//Kill the decode thread and close the log files, called from destructor
// not used atm, but could be used to abort a decode operation from the GUI
void LogFileDecoder::abortDecode()
{
    mutex->lock();
    quit = true;
    mutex->unlock();

    wait();

    finishDecode();
}

//Close log files, called from decode thread OR abortDecode() once the thread has stopped
void LogFileDecoder::finishDecode()
{
    mutex->lock();
    if (logFile->isOpen())
        logFile->close();

    if (csvFile->isOpen()) {
        output.flush();
        csvFile->close();
    }
    mutex->unlock();
}

//Main decode thread logic - this is the code that executes in a separate thread
void LogFileDecoder::run()
{
    time_t currentTimeStamp = -1;
    //The two lists are linked, i.e. should maintain the same size at all times
    // register names and the corresponding most recently read value
    QList<QString> registerNames;
    QList<QString> registerValues;

    QDateTime time = QDateTime();
    //We have to pretend that the time is UTC even though it's not
    // as otherwise Qt tries to compensate for daylight savings etc.
    time.setTimeSpec(Qt::UTC);

    while (!logFile->atEnd())
    {
        mutex->lock();
        if (quit)
            return;
        
        //Read a line at a time from the log
        //Format is {register_name}={value},{unix timestamp}
        QByteArray logData = logFile->readLine();
        QString registerName = logData.split('=').at(0);
        QString registerValue = logData.split('=').at(1).split(',').at(0);
        time_t timeStamp = logData.split('=').at(1).split(',').at(1).trimmed().toInt();
        
        //only process valid timestamps - ignores some data though
        if (timeStamp > 0) {
            //If this is the first row
            if (currentTimeStamp == -1)
                currentTimeStamp = timeStamp;

            //Every time the time changes output a line into the csv
            if (currentTimeStamp != timeStamp) {
                time.setTime_t(currentTimeStamp);
                output << time.toString("yyyy/MM/dd hh:mm:ss");
                foreach(QString reg, registerValues) {
                    output << "," << reg;
                }
                output << endl;
                currentTimeStamp = timeStamp;
            }
        }
        mutex->unlock();
        
        /* note: this is the cause of the "feature" that causes the columns to be in a different order
         * depending on the order the appear in the log file. to fix this we would have to hard-code
         * a list of register values and sacrifice the ability to deal with new register names
         */

        //If the register is already in the list of names, set the corresponding value
        // if it isn't, add it to the end of the list and set the value
        if (registerNames.contains(registerName)) {
            registerValues[registerNames.indexOf(registerName)] = registerValue;
        } else {
            registerNames.append(registerName);
            registerValues.insert(registerNames.indexOf(registerName), registerValue);
        }
    }

    //Need to output the very last line before finishing
    mutex->lock();
    time.setTime_t(currentTimeStamp);
    output << time.toString("yyyy/MM/dd hh:mm:ss");
    foreach(QString reg, registerValues) {
        output << "," << reg;
    }
    output << endl;

    //Create a new csv file with the proper name, output the header (register names) to it, and
    // copy over the contents of the temporary csv file
    csvFile->seek(0);
    QString ofilename = csvFile->fileName().remove("~1~");
    QFile *oFile = new QFile(ofilename);
    if (oFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
        QTextStream ostream(oFile);
        csvFile->close();
        csvFile->open(QIODevice::ReadOnly | QIODevice::Text);
        ostream << "Time";
        foreach (QString name, registerNames) {
            ostream << "," << name;
        }
        ostream << endl << csvFile->readAll() << endl;
        oFile->close();
        csvFile->remove();
    }
    delete oFile;
    mutex->unlock();

    //Close files as appropriate
    finishDecode();

    //Let the GUI thread know we finished the decode operation
    emit decodeFinished();
}
